package jp.co.katagaki.crossword.create;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

import jp.co.katagaki.crossword.io.reader.PuzzleWordReader;
import jp.co.katagaki.crossword.io.writer.PuzzleWordWriter;
import jp.co.katagaki.crossword.puzzle.Genre;
import jp.co.katagaki.crossword.puzzle.Word;
import jp.co.katagaki.util.Assertions;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

public class PuzzleWordEditer {

    /**
     * {@link Word}を単語の文字列で比較する{@link Comparator}。
     */
    private static final Comparator<Word> WORD_COMPARATOR = new Comparator<Word>() {
        @Override
        public int compare(Word o1, Word o2) {
            return o1.getWord().compareTo(o2.getWord());
        }
    };

    /** 単語 Reader */
    private PuzzleWordReader wordReader;
    /** 単語 Writer */
    private PuzzleWordWriter wordWriter;

    /**
     * 案となる単語の情報を基に該当する単語一覧を取得する。 <br />
     * 指定された単語の情報、それに該当する単語は以下の通り。
     * <ul>
     * <li>おおさか</li>
     * <li>おきなわ</li>
     * <li>かながわ</li>
     * </ul>
     *
     * <pre>
     * 指定された単語の情報: [文字位置=1, 文字="お"]
     * 該当する単語：おおさか、おきなわ
     *
     * 指定された単語の情報: [文字位置=1, 文字="お"][文字位置=4, 文字="わ"]
     * 該当する単語：おきなわ
     * </pre>
     *
     * @param wordSize
     *            単語の文字数
     * @param plans
     *            案となる単語情報一覧
     * @return 該当する単語一覧
     */
    public List<Word> findByWordPlan(int wordSize, WordPlanPair... plans) {
        verifyGet(wordSize, plans);

        // 検索対象の文字列（正規表現）を生成
        List<WordPlanPair> planList = Lists.newArrayList(Arrays.asList(plans));
        StringBuilder sb = new StringBuilder(wordSize);
        for (int wordPoint = 1; wordPoint <= wordSize; wordPoint++) {
            boolean hasPoint = false;
            WordPlanPair hasPlanWord = null;
            for (WordPlanPair plan : planList) {
                if (plan.getWordPoint() == wordPoint) {
                    hasPoint = true;
                    hasPlanWord = plan;
                    break;
                }
            }
            if (hasPoint) {
                sb.append(hasPlanWord.getWordChar());
            } else {
                sb.append(".");
            }
        }
        return findMatchesWord(sb.toString());
    }

    public void registerWord(String word, Genre genre, String hint) {
        Assertions.assertNotNull("", word);
        Assertions.assertNotNull("", genre);
        Assertions.assertNotNull("", hint);

        Word value = new Word();
        value.setWord(word);
        value.setGenre(genre);
        value.setHint(hint);

        wordWriter.write(value);
    }

    /**
     * 指定した文字列が単語として有効かどうかを返す。<br />
     * 既に単語として登録されている場合は false を返す。
     *
     * @param target
     *            単語として登録する文字列
     * @return 単語として有効な場合は true
     */
    public boolean validWord(String target) {
        Assertions.assertNotNull("単語として登録する文字列", target);

        return findMatchesWord(target).isEmpty();
    }

    private void verifyGet(int wordSize, WordPlanPair... plans) {
        if (wordSize < 1) {
            throw new IllegalArgumentException(
                    "キーワードの文字列サイズに 0 より小さい値が指定されました: " + wordSize);
        }
        Set<Integer> wordPointSet = Sets
                .newHashSetWithExpectedSize(plans.length);
        for (WordPlanPair plan : plans) {
            if (!wordPointSet.add(plan.getWordPoint())) {
                throw new IllegalArgumentException("案となる文字位置が重複しています: "
                        + plan.getWordPoint());
            }
        }
    }

    private List<Word> findMatchesWord(String search) {
        List<Word> result = Lists.newArrayList();
        for (Word word : wordReader.readAll()) {
            if (word.matches(search)) {
                result.add(word);
            }
        }

        Collections.sort(result, WORD_COMPARATOR);
        return result;
    }

    /**
     * 単語 Reader を設定する。
     *
     * @param wordReader
     *            単語 Reader
     */
    public void setWordReader(PuzzleWordReader wordReader) {
        this.wordReader = wordReader;
    }

    /**
     * 単語 Writer を設定する。
     *
     * @param wordWriter
     *            単語 Writer
     */
    public void setWordWriter(PuzzleWordWriter wordWriter) {
        this.wordWriter = wordWriter;
    }
}
